<?php namespace App\Core\Repository;

use App\Core\Model;
use App\Core\Exceptions\RuntimeError;

abstract class Repository {
    
    use Listenable, Criteriable, Sortable;

    protected $model;
    
    protected $query;
    
    protected $softDelete = false;

    public function __construct(){
        $this->makeModel();
        $this->resetQuery();
        $this->resetCriteria();
    }

    public abstract function model();
    
    public function makeModel(){
        return $this->setModel($this->model());
    }

    public function setModel($model){
        if(!$model instanceof Model){
            throw new RuntimeError();
        }
        $this->model = $model;
        return $this;
    }
    
    public function getModel(){
        return $this->model;
    }
    
    public function resetQuery(){
        $this->query = $this->getModel()->newQuery();
        return $this;
    }
    
    public function getQuery(){
        return $this->query;
    }
    
    public function softDelete($value = null){
        if($value === null){
            return $this->softDelete;
        }
        $this->softDelete = $value;
    }
    
    public function create(array $data){
        $this->saveData = $data;
        $this->fireEvent('creating');
        $newModel = $this->model->newInstance();
        foreach($this->saveData as $field => $value){
            $newModel->$field = $value;
        }
        $newModel->save();
        $this->fireEvent('created', $newModel);
        $this->saveData = null;
        return $newModel;
    }

    public function update(array $data){
        $this->saveData = $data;
        $this->applyCriterias();
        $models = $this->query->get();
        foreach($models as $model){
            $this->fireEvent('updating', $model);
            foreach($this->saveData as $field => $value){
                $model->$field = $value;
            }
            $model->save();
            $this->fireEvent('updated', $model);
        }
        $this->saveData = null;
        return $models;
    }
    
    public function delete(){
        $this->applyCriterias();
        $models = $this->query->get();
        foreach($models as $model){
            $this->fireEvent('deleting', $model);
            if($this->softDelete){
                $model->is_delete = 1;
                $model->save();
            }else{
                $model->delete();
            }
            $this->fireEvent('deleted', $model);
        }
        return $models;
    }

    public function all($columns = ['*']){
        $columnsArr = is_array($columns) ? $columns : func_get_args();
        $this->applyCriterias();
        return $this->query->get($columnsArr);
    }

    /**
     * @param array $relations
     * @return $this
     */
    public function with($relations){
        $relationsArr = is_array($relations) ? $relations : func_get_args();
        $this->query->with($relationsArr);
        return $this;
    }

    /**
     * @param  string $value
     * @param  string $key
     * @return array
     */
    public function lists($value, $key = null){
        $this->applyCriterias();
        $lists = $this->query->lists($value, $key);
        if (is_array($lists)) {
            return $lists;
        }
        return $lists->all();
    }

    /**
     * @param int $perPage
     * @param array $columns
     * @return mixed
     */
    public function paginate($perPage = 25, $columns = array('*')){
        $this->applyCriterias();
        return $this->query->paginate($perPage, $columns);
    }
    
    public function page($page = 0, $itemPerPage = 15){
        $this->applyCriterias();
        return $this->query->skip($itemPerPage*$page)->take($itemPerPage)->get();
    }
    
    public function count(){
        $this->applyCriterias();
        return $this->query->count();
    }
    
    public function sum($field){
        $this->applyCriterias();
        return $this->query->sum($field);
    }
    
    public function max($field){
        $this->applyCriterias();
        return $this->query->max($field);
    }
    
    public function min($field){
        $this->applyCriterias();
        return $this->query->min($field);
    }
    
    public function avg($field){
        $this->applyCriterias();
        return $this->query->avg($field);
    }
    
    public function first(){
        $this->applyCriterias();
        return $this->query->first();
    }

    /**
     * @param $id
     * @param array $columns
     * @return mixed
     */
    public function find($id){
        $this->applyCriterias();
        return $this->query->find($id);
    }
}